/*
 * SoundMachine.cpp
 *
 *  Created on: 2011-2-25
 *      Author: hao
 */
#include "SoundMachine.h"

#include <cstring>

SoundMachine * SoundMachine::pSelf = NULL;
bool SoundMachine::bEnd = true;

SoundMachine::SoundMachine() :
pMainLoop(NULL), pContext(NULL), pStream(NULL),
stream_state(StreamState::STREAM_STOP) {
}

SoundMachine::~SoundMachine() {

    Stop();

    pSelf = NULL;

}

SoundMachine * SoundMachine::getSoundMachine() {


    if (pSelf == NULL) pSelf = new SoundMachine;

    return pSelf;

}

void SoundMachine::Init() {

    if (!bEnd) return;

    bEnd = false;

    pMainLoop = pa_threaded_mainloop_new();

    if (!pMainLoop)
        throw Error(ErrorTag::MAINLOOP_CREATE_FAILED);

    pContext = pa_context_new( pa_threaded_mainloop_get_api(pMainLoop), "SoundMachine");

    if (!pContext)
        throw Error(ErrorTag::CREATE_CONTEXT_FAILED);

    pa_context_set_state_callback(pContext, context_state_callback, NULL);

    pa_context_connect(pContext, NULL, PA_CONTEXT_NOFLAGS, NULL);

    //main loop

    pa_threaded_mainloop_lock(pMainLoop);

    pa_threaded_mainloop_start(pMainLoop);

    while (pa_context_get_state(pContext) != PA_CONTEXT_READY)
        pa_threaded_mainloop_wait(pMainLoop);

    pa_threaded_mainloop_unlock(pMainLoop);

}

void SoundMachine::stream_write_callback(pa_stream * stream, size_t length,
        void * usrData) {

    if (bEnd) {
        pa_threaded_mainloop_signal(pSelf->pMainLoop, 0);
        return;
    }

    if (!stream) {
        pa_threaded_mainloop_signal(pSelf->pMainLoop, 0);
        return;
    }

    if (!pSelf->state_write_function) {
        pa_threaded_mainloop_signal(pSelf->pMainLoop, 0);
        return;
    }

    void * data = pa_xmalloc(length);

    size_t readSize = 0;

    if (!pSelf->state_write_function(data, length, &readSize) || readSize > length) {

        free(data);

        pa_operation_unref(pa_stream_drain(pSelf->pStream,
                stream_drain_complete, NULL));
        pSelf->state_write_function = NULL;

        pa_threaded_mainloop_signal(pSelf->pMainLoop, 0);

        return;

    }

    pa_stream_write(stream, data, readSize, pa_xfree, 0, PA_SEEK_RELATIVE);

    if (readSize < length) {
        pa_operation_unref(pa_stream_drain(stream,
                stream_drain_complete, NULL));
        pSelf->state_write_function = NULL;
    }

    pa_threaded_mainloop_signal(pSelf->pMainLoop, 0);

}

void SoundMachine::context_state_callback(pa_context * context, void * usrData) {

    if (context) {

        pa_context_state_t state = pa_context_get_state(context);

        if (state == PA_CONTEXT_TERMINATED || state == PA_CONTEXT_FAILED) {
            //pSelf->mainloop_api->quit(pSelf->mainloop_api, 0);
            pa_threaded_mainloop_signal(pSelf->pMainLoop, 0);

        } else if (state == PA_CONTEXT_READY) {
            pa_threaded_mainloop_signal(pSelf->pMainLoop, 0);
        }

    }

}

void SoundMachine::stream_state_callback(pa_stream * stream, void * userData) {
    if (stream) {
        switch (pa_stream_get_state(stream)) {
            case PA_STREAM_READY:
            case PA_STREAM_FAILED:
            case PA_STREAM_TERMINATED:
                pa_threaded_mainloop_signal(pSelf->pMainLoop, 0);
            default:
                ;
        }
    }
}

void SoundMachine::CreateStream(const std::function<bool(void *, size_t,
        size_t *)> & write_function,
        const std::function<void()> & drain_function,
        pa_sample_format_t format, int channels, int rate) {

    if (pStream) {
        return;
    }

    state_write_function = write_function;

    stream_drain_function = drain_function;

    pa_sample_spec sample_spec;

    sample_spec.channels = channels;

    sample_spec.format = format;

    sample_spec.rate = rate;

    pa_threaded_mainloop_lock(pMainLoop);

    pStream = pa_stream_new(pContext, "Playingback Stream", &sample_spec, NULL);

    if (!pStream)
        throw Error(ErrorTag::CREATE_STREAM_FAILED);

    pa_stream_set_write_callback(pStream, stream_write_callback, NULL);
    pa_stream_set_state_callback(pStream, stream_state_callback, NULL);

    pa_stream_connect_playback(pStream, NULL, NULL, pa_stream_flags_t(
            PA_STREAM_AUTO_TIMING_UPDATE | PA_STREAM_INTERPOLATE_TIMING | PA_STREAM_ADJUST_LATENCY | PA_STREAM_START_CORKED),
            pa_cvolume_set(&cVolume, 2, PA_VOLUME_NORM), NULL);

    stream_state = StreamState::STREAM_PAUSE;

    while (PA_STREAM_READY != pa_stream_get_state(pStream))
        pa_threaded_mainloop_wait(pMainLoop);

    pa_threaded_mainloop_unlock(pMainLoop);

}

void SoundMachine::stream_drain_complete(pa_stream * stream, int success,
        void * usrData) {

    if (!stream)
        return;

    if (!success) {

        throw Error(ErrorTag::STREAM_DRAIN_FAILED);

    }

    /*pa_stream_disconnect(pSelf->pStream);

     pa_stream_unref(pSelf->pStream);

     pSelf->pStream = NULL;*/

    if (pSelf->stream_drain_function)
        pSelf->stream_drain_function();

    pa_threaded_mainloop_signal(pSelf->pMainLoop, 0);

}

void SoundMachine::Stop() {

    if (bEnd) return;

    bEnd = true;


    pa_threaded_mainloop_stop(pMainLoop);
    if (pStream) {

        pa_stream_disconnect(pStream);

        pa_stream_unref(pStream);

        pStream = NULL;

    }

    if (pContext) {

        pa_context_disconnect(pContext);

        pa_context_unref(pContext);

        pContext = NULL;

    }
    
    pa_threaded_mainloop_wait(pMainLoop);

    pa_threaded_mainloop_free(pMainLoop);

}

void SoundMachine::setVolumeDB(double db) {

    if (!pStream) {
        return;
    }

    if (!state_write_function) {
        return;
    }

    const pa_sample_spec * pSpec = pa_stream_get_sample_spec(pStream);

    pa_cvolume_set(&cVolume, pSpec->channels, pa_sw_volume_from_dB(db));

    pa_operation * pOperation;

    pa_threaded_mainloop_lock(pMainLoop);

    pOperation = pa_context_set_sink_input_volume(pContext, pa_stream_get_index(pStream),
            &cVolume, context_success_callback, NULL);

    while (pa_operation_get_state(pOperation) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(pMainLoop);
    }

    pa_operation_unref(pOperation);

    pa_threaded_mainloop_unlock(pMainLoop);

}

void SoundMachine::setVolumeLinear(double linear) {

    if (!pStream) {
        return;
    }

    if (!state_write_function) {
        return;
    }

    const pa_sample_spec * pSpec = pa_stream_get_sample_spec(pStream);

    pa_cvolume_set(&cVolume, pSpec->channels, pa_sw_volume_from_linear(linear));

    pa_operation * pOperation;

    pa_threaded_mainloop_lock(pMainLoop);

    pOperation = pa_context_set_sink_input_volume(pContext, pa_stream_get_index(pStream),
            &cVolume, context_success_callback, NULL);

    while (pa_operation_get_state(pOperation) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(pMainLoop);
    }

    pa_operation_unref(pOperation);

    pa_threaded_mainloop_unlock(pMainLoop);

}

void SoundMachine::setChannelVolumeDb(int channel, double db) {

    if (!pStream) {
        return;
    }

    if (!state_write_function) {
        return;
    }

    pa_operation * pOperation;

    pa_threaded_mainloop_lock(pMainLoop);

    const pa_sample_spec * pSpec = pa_stream_get_sample_spec(pStream);

    if (channel >= pSpec->channels) {
        pa_threaded_mainloop_unlock(pMainLoop);
        return;
    }

    cVolume.values[channel] = pa_sw_volume_from_dB(db);

    pOperation = pa_context_set_sink_input_volume(pContext, pa_stream_get_index(pStream),
            &cVolume, context_success_callback, NULL);

    while (pa_operation_get_state(pOperation) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(pMainLoop);
    }

    pa_operation_unref(pOperation);

    pa_threaded_mainloop_unlock(pMainLoop);

}

void SoundMachine::setChannelVolumeLinear(int channel, double linear) {

    if (!pStream) {
        return;
    }

    if (!state_write_function) {
        return;
    }

    pa_operation * pOperation;

    pa_threaded_mainloop_lock(pMainLoop);

    const pa_sample_spec * pSpec = pa_stream_get_sample_spec(pStream);

    if (channel >= pSpec->channels) {
        pa_threaded_mainloop_unlock(pMainLoop);
        return;
    }

    cVolume.values[channel] = pa_sw_volume_from_linear(linear);

    pOperation = pa_context_set_sink_input_volume(pContext, pa_stream_get_index(pStream),
            &cVolume, context_success_callback, NULL);

    while (pa_operation_get_state(pOperation) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(pMainLoop);
    }

    pa_operation_unref(pOperation);

    pa_threaded_mainloop_unlock(pMainLoop);
}

void SoundMachine::StreamPause() {

    if (!pStream) {
        return;
    }

    if (!state_write_function) {
        return;
    }

    if (stream_state != StreamState::STREAM_PLAYING) {
        return;
    }

    pa_operation * poperation;

    pa_threaded_mainloop_lock(pMainLoop);

    poperation = pa_stream_cork(pStream, 1, stream_success_callback, NULL);

    while (pa_operation_get_state(poperation) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(pMainLoop);
    }

    pa_operation_unref(poperation);

    pa_threaded_mainloop_unlock(pMainLoop);

    stream_state = StreamState::STREAM_PAUSE;

}

void SoundMachine::StreamResume() {

    if (!pStream) {
        return;
    }

    if (!state_write_function) {
        return;
    }

    if (stream_state != StreamState::STREAM_PAUSE) {
        return;
    }

    pa_operation * poperation;

    pa_threaded_mainloop_lock(pMainLoop);

    poperation = pa_stream_cork(pStream, 0, stream_success_callback, NULL);

    while (pa_operation_get_state(poperation) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(pMainLoop);
    }

    pa_operation_unref(poperation);

    pa_threaded_mainloop_unlock(pMainLoop);

    stream_state = StreamState::STREAM_PLAYING;

}

void SoundMachine::StopStream() {

    if (!pStream) {
        return;
    }

    if (!state_write_function) {
        return;
    }

    if (stream_state != StreamState::STREAM_PLAYING) {
        return;
    }

    pa_operation * pObject;

    pa_threaded_mainloop_lock(pMainLoop);

    pObject = pa_stream_flush(pStream, stream_success_callback, NULL);

    while (pa_operation_get_state(pObject) == PA_OPERATION_RUNNING)
        pa_threaded_mainloop_wait(pMainLoop);

    pa_operation_unref(pObject);

    pObject = pa_stream_cork(pStream, 1, stream_success_callback, NULL);

    while (pa_operation_get_state(pObject) == PA_OPERATION_RUNNING) {
        pa_threaded_mainloop_wait(pMainLoop);
    }

    pa_operation_unref(pObject);

    pSelf->stream_state = StreamState::STREAM_STOP;

    pa_threaded_mainloop_unlock(pMainLoop);

}

void SoundMachine::DestroyStream() {

    if (!pStream)
        return;

    StopStream();

    pa_stream_disconnect(pStream);

    pa_stream_unref(pStream);

    pStream = NULL;

}

int SoundMachine::StreamGetState() {

    return stream_state;

}

unsigned long long SoundMachine::getCurTime() {
    if (!pStream) {
        return 0;
    }
    if (!state_write_function) {
        return 0;
    }

    pa_usec_t tmp;
    pa_threaded_mainloop_lock(pMainLoop);
    pa_stream_get_time(pStream, &tmp);
    pa_threaded_mainloop_unlock(pMainLoop);
    return tmp;
}
